 // 解析指令的方法
let CompierUtil = {
    getValue(vm, expr) {
        // 根据表达式获取值（school.name => alex，message => <h1>哈哈</h1>）
        return expr.split('.').reduce((data, current) => {
            return data[current]
        }, vm.$data)
    },
    setValue(vm,expr,value) {
        // 对表达式对应的属性重新赋值
        expr.split('.').reduce((data, current, index, arr) => {
            if (index === arr.length -1 ) {
                return data[current] = value
            }
            return data[current]
        }, vm.$data)
    },
    model(node, expr, vm) {
        // 定义更新元素内容的方法
        let fn = this.updater['modelUpdater']
        // 根据表达式获取值
        let value = this.getValue(vm, expr)
        // 初始化视图渲染
        fn(node, value)
        // 对输入框（v-model）订阅
        new Watcher(vm, expr, (newValue) => {
            // 数据变化执行，将视图更新
            fn(node, newValue)
        })
        // 监测视图的更新
        node.addEventListener('input', (e) => {
            let newValue = e.target.value;
            // 将新值更新到数据中（vm.$data）
            this.setValue(vm, expr, newValue)
        })
    },
    html(node,expr,vm) {
        // 定义更新元素内容的方法
        let fn = this.updater['htmlUpdater']
        // 根据表达式获取值
        let value = this.getValue(vm, expr)
        // 初始化视图渲染
        fn(node, value)
        // 对v-html订阅
        new Watcher(vm, expr, (newValue) => {
            // 数据变化执行，将视图更新
            fn(node, newValue)
        })
    },
    on(node,expr,vm,eventName) {
        // 对on指令对应的元素进行事件监听 v-on:click="change"
        node.addEventListener(eventName, (e) => {
            // expr => change
            // change 方法内的 this 指向 vm
            vm[expr].call(vm,e)
        })
    },
    // 获取文本框表达式对应的数据
    getContentValue(vm,expr) {
        let value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            // console.log(args) ["{{school.name}}", "school.name", 0, "{{school.name}}{{school.age}}"]
            return this.getValue(vm, args[1])
        })
        return value
    },
    text(node, expr, vm) {
        // 定义更新文本内容的方法
        let fn = this.updater['textUpdater']
        // 对每一文本进行数据替换
        // expr => {{school.name}}{{school.age}}
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            // 给每一个{{}}加入观察者
            new Watcher(vm, args[1], () => {
                // 对每一个{{}}所在的元素节点更新
                fn(node, this.getContentValue(vm,expr) )
            })
            let value = this.getValue(vm, args[1])
            return value
        })
        // 初始化视图渲染
        fn(node, content)
    },
    updater: {
        // 输入框更新方法
        modelUpdater(node, value) {
            node.value = value
        },
        // 文本更新方法
        textUpdater(node, value) {
            node.textContent = value
        },
        // 富文本更新方法
        htmlUpdater(node,value) {
            node.innerHTML = value
        }
    }
}
// 编译模版（核心代码）
class Compiper {
    constructor(el, vm) {
        // document.getElementById获取到的是动态的 document.querySelector获取的是静态的
        // 获取根元素
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 将根元素获取到内存中
        let fragment = this.nodeToFragment(this.el)
        // 编译模版，将数据替换模版中的表达式（{{school.name}}\v-model="school.name"）
        this.compier(fragment)
        // 将替换后的内容塞到页面
        this.el.appendChild(fragment)
    }
    compier(node) {
        let childNode = node.childNodes; // 一层子元素，不包括儿子的儿子 类数组
        [...childNode].forEach(child => {
            if (this.isElementNode(child)) {
                // 元素节点处理
                this.compierElement(child)
                // 递归将所有元素编译
                this.compier(child)
            } else {
                // 文本节点处理
                this.compierText(child)
            }
        })
    }
    // 判断属性是否为指令
    isDirective(attrName) {
        // startsWith es6的方法
        return attrName.startsWith('v-')
    }
    // 编译元素
    compierElement(node) {
        // 获取元素属性
        let attributes = node.attributes; // 类数组
        [...attributes].forEach(attr => {
            // console.log(attr); type=text v-model=school.name
            let {
                name,
                value: expr
            } = attr
            // 判断属性是否为指令
            if (this.isDirective(name)) { // v-model v-html v-bind v-on:click
                let [, directive] = name.split('-') // directive:model\html\bind\on:click
                // 如果是on:click进行分割
                let [directiveName, eventName] = directive.split(':')
                // 解析指令
                CompierUtil[directiveName](node, expr, this.vm, eventName)
            }
        })
    }
    // 编译文本
    compierText(node) {
        // 获取文本节点的内容
        let content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            // 解析指令
            CompierUtil['text'](node, content, this.vm)
        }
    }
    nodeToFragment(node) {
        // 创建文档碎片
        let fragment = document.createDocumentFragment()
        let firstChild
        // 将模版添加到文档碎片这种
        while (firstChild = node.firstChild) {
            // appendChild可以将模版元素移到文档碎片中
            fragment.appendChild(firstChild)
        }
        return fragment
    }
    isElementNode(node) {
        // 判断是不是元素 
        //1.元素节点 2.属性节点 3.文本节点
        return node.nodeType === 1
    }
}
// 创建自己的vue类
class Vue {
    constructor(options) {
        // options:实例化时传进来的参数
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        let methods = options.methods;
        // 判断根元素是否存在
        if (this.$el) {
            // 数据劫持，给每一个属性添加一个dep
            new Observer(this.$data)
            // 代理 computed 数据到this.$data上，以便可以直接通过this.xxx访问数据
            for (let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get:() => {
                        return computed[key].call(this)
                    }
                })
            }
            // 代理 methods 数据到实例上，以便可以直接通过this.xxx访问数据
            for (let key in methods) {
                Object.defineProperty(this, key, {
                    get:() => {
                        return methods[key]
                    }
                })
            }
            // 将this.$data 上的数据代理到this上
            this.proxyVm(this.$data)
            // 编译模版数据
            new Compiper(this.$el, this)
        }
    }
    proxyVm(data) {
        // 访问this.xxx 即 this.$data.xxx
        for (let key in data) {
            Object.defineProperty(this, key, {
                // 当且仅当该属性的 configurable 为 true 时，该属性描述符才能够被改变，同时该属性也能从对应的对象上被删除。默认为 false。
                configurable: false,
                // 当且仅当该属性的 enumerable 为true时，该属性才能够出现在对象的枚举属性中。默认为 false。
                enumerable: false,
                get(){
                    return data[key]
                },
                set(newValue){
                    data[key] = newValue
                }
            })
        }
    }
}
